package com.songshuang.myutilsboot.stlf.core;

import com.songshuang.myutilsboot.stlf.model.Models;

import java.util.*;

public final class LrPredictor {
    private LrPredictor() {}

    public static boolean predict(Models.InputParameter ip, Models.ForecastInfo fi) {
        if (ip.global_rc == null) return false;
        int sdnum = (int) (ip.global_rc.sdnum > 0 ? ip.global_rc.sdnum : 96);
        List<String> infacts = ip.global_rc.Infacts;
        double pwrtol = ip.global_rc.pwrtol;

        // 准备历史样本 X/y：行数 hisDays*sdnum，列数 = infacts.size()（不含截距），LinearRegression.fit 包含截距
        Map<String, Map<Long, Models.HisData>> byDate = new LinkedHashMap<>();
        if (ip.his_data == null || ip.his_data.isEmpty()) return false;
        for (Models.HisData d : ip.his_data) byDate.computeIfAbsent(d.Date, k -> new HashMap<>()).put(d.Sd, d);
        int hisDays = byDate.size();
        int cols = infacts.size();
        int rows = hisDays * sdnum;
        double[][] X = new double[rows + 1][cols + 1];
        double[] y = new double[rows + 1];

        int r = 0;
        for (Map.Entry<String, Map<Long, Models.HisData>> e : byDate.entrySet()) {
            String date = e.getKey();
            Map<Long, Models.HisData> sdMap = e.getValue();
            for (int sd = 1; sd <= sdnum; sd++) {
                r++;
                Models.HisData d = sdMap.getOrDefault((long) sd, defaultHis(date, sd));
                fillFeaturesLR(X[r], d, ip.global_rc.pv_nlat, sdnum, infacts);
                y[r] = d.MW0; // LR 使用未缩放的 MW0
            }
        }

        double[] b = LinearRegression.fit(X, y, rows, cols);

        // 生成 His_Report 全时段
        List<Models.SigSdInf> hisList = new ArrayList<>();
        r = 0;
        for (Map.Entry<String, Map<Long, Models.HisData>> e : byDate.entrySet()) {
            String date = e.getKey();
            Map<Long, Models.HisData> sdMap = e.getValue();
            for (int sd = 1; sd <= sdnum; sd++) {
                Models.HisData d = sdMap.getOrDefault((long) sd, defaultHis(date, sd));
                double[] fx = new double[cols + 1];
                fillFeaturesLR(fx, d, ip.global_rc.pv_nlat, sdnum, infacts);
                double mw = LinearRegression.predict(b, fx);
                if (mw < pwrtol) mw = 0;
                Models.SigSdInf inf = new Models.SigSdInf();
                inf.Date = date; inf.Sd = sd; inf.MW0 = d.MW0; inf.MW = mw;
                inf.Err = mw - d.MW0; inf.Pct = d.MW0 != 0 ? inf.Err * 100.0 / d.MW0 : 0;
                hisList.add(inf);
            }
        }
        fi.His_Report = hisList;

        // 生成 Fore_Report 全时段（按 for_data 的日期集合）
        Map<String, Map<Long, Models.ForData>> forByDate = new LinkedHashMap<>();
        if (ip.for_data != null) for (Models.ForData d : ip.for_data) forByDate.computeIfAbsent(d.Date, k -> new HashMap<>()).put(d.Sd, d);
        List<Models.SigSdInf> foreList = new ArrayList<>();
        for (Map.Entry<String, Map<Long, Models.ForData>> e : forByDate.entrySet()) {
            String date = e.getKey();
            Map<Long, Models.ForData> sdMap = e.getValue();
            for (int sd = 1; sd <= sdnum; sd++) {
                Models.ForData d = sdMap.getOrDefault((long) sd, defaultFor(date, sd));
                double[] fx = new double[cols + 1];
                fillFeaturesLR(fx, d, ip.global_rc.pv_nlat, sdnum, infacts);
                double mw = LinearRegression.predict(b, fx);
                if (mw < pwrtol) mw = 0;
                Models.SigSdInf inf = new Models.SigSdInf();
                inf.Date = date; inf.Sd = sd; inf.MW0 = 0; inf.MW = mw; inf.Err = 0; inf.Pct = 0;
                foreList.add(inf);
            }
        }
        fi.Fore_Report = foreList;
        return true;
    }

    private static void fillFeaturesLR(double[] target, Models.HisData d, double lat, int sdnum, List<String> infacts) {
        int idx = 1;
        for (String f : infacts) {
            switch (f) {
                case "sunalt": target[idx++] = sunalt(d.Date, d.Sd, lat, sdnum); break;
                case "wea_Temp": target[idx++] = d.Temp; break;
                case "wea_windSpeed": target[idx++] = d.windSpeed; break;
                case "wea_windDir": target[idx++] = d.windDir; break;
                case "wea_Pa": target[idx++] = d.Pa; break;
                case "wea_Transp": target[idx++] = d.Transp; break;
                case "wea_Humid": target[idx++] = d.Humid; break;
                case "wea_Cloud": target[idx++] = d.Cloud; break;
                case "wea_Dni": target[idx++] = d.Dni; break;
                default: target[idx++] = 0; break;
            }
        }
    }
    private static void fillFeaturesLR(double[] target, Models.ForData d, double lat, int sdnum, List<String> infacts) {
        int idx = 1;
        for (String f : infacts) {
            switch (f) {
                case "sunalt": target[idx++] = sunalt(d.Date, d.Sd, lat, sdnum); break;
                case "wea_Temp": target[idx++] = d.Temp; break;
                case "wea_windSpeed": target[idx++] = d.windSpeed; break;
                case "wea_windDir": target[idx++] = d.windDir; break;
                case "wea_Pa": target[idx++] = d.Pa; break;
                case "wea_Transp": target[idx++] = d.Transp; break;
                case "wea_Humid": target[idx++] = d.Humid; break;
                case "wea_Cloud": target[idx++] = d.Cloud; break;
                case "wea_Dni": target[idx++] = d.Dni; break;
                default: target[idx++] = 0; break;
            }
        }
    }

    private static double sunalt(String date, long sd, double lat, int sdnum) {
        String time = sdToTime((int) sd, sdnum);
        double thet = thetFun(date);
        double deltDeg = deltFun(thet);
        double delt = Math.toRadians(deltDeg);
        double phi = Math.toRadians(lat);
        double t = timeToSeconds(time);
        double ta0 = (t / 3600.0 - 12.0) * 15.0;
        double ta = Math.toRadians(ta0);
        double sinh = Math.sin(phi) * Math.sin(delt) + Math.cos(phi) * Math.cos(delt) * Math.cos(ta);
        return Math.max(0, Math.toDegrees(Math.asin(sinh)));
    }
    private static String sdToTime(int sd, int sdnum) {
        double secondsPerSlot = 24 * 3600.0 / sdnum;
        double seconds = (sd - 1) * secondsPerSlot;
        int hh = (int) (seconds / 3600);
        int mm = (int) ((seconds % 3600) / 60);
        int ss = (int) (seconds % 60);
        return String.format("%02d:%02d:%02d", hh, mm, ss);
    }
    private static double thetFun(String date) {
        int year = Integer.parseInt(date.split("/")[2]);
        double n0 = 79.6764 + 0.2422 * (year - 1985) - (int) ((year - 1985) / 4);
        double nt = dayOfYear(date) - n0;
        return 360.0 * nt / 365.2422;
    }
    private static double deltFun(double thetDeg) {
        double rad = Math.toRadians(thetDeg);
        double ssin = 23.2567 * Math.sin(rad) + 0.1149 * Math.sin(2 * rad) - 0.1712 * Math.sin(3 * rad);
        double scos = -0.7580 * Math.cos(rad) + 0.3656 * Math.cos(2 * rad) + 0.0201 * Math.cos(3 * rad);
        return 0.3723 + ssin + scos;
    }
    private static double timeToSeconds(String time) {
        String[] p = time.split(":");
        int hh = Integer.parseInt(p[0]);
        int mm = Integer.parseInt(p[1]);
        int ss = p.length > 2 ? Integer.parseInt(p[2]) : 0;
        return hh * 3600.0 + mm * 60.0 + ss;
    }
    private static int dayOfYear(String date) {
        String[] parts = date.split("/");
        int month = Integer.parseInt(parts[0]);
        int day = Integer.parseInt(parts[1]);
        int year = Integer.parseInt(parts[2]);
        int[] daysInMonth = {31,28,31,30,31,30,31,31,30,31,30,31};
        if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) daysInMonth[1] = 29;
        int doy = day;
        for (int i = 0; i < month - 1; i++) doy += daysInMonth[i];
        return doy;
    }

    private static Models.HisData defaultHis(String date, int sd) {
        Models.HisData d = new Models.HisData();
        d.Date = date; d.Sd = sd; d.MW0 = 0; d.Temp = 0; d.windSpeed = 0; d.windDir = 0; d.Pa = 0; d.Transp = 0; d.Humid = 0; d.Cloud = 0; d.Dni = 0; d.Descr = ""; return d;
    }
    private static Models.ForData defaultFor(String date, int sd) {
        Models.ForData d = new Models.ForData();
        d.Date = date; d.Sd = sd; d.MW = 0; d.Temp = 0; d.windSpeed = 0; d.windDir = 0; d.Pa = 0; d.Transp = 0; d.Humid = 0; d.Cloud = 0; d.Dni = 0; d.Descr = ""; return d;
    }
}


